// ##########################################################################
// #                                                                        #
// #                              CLOUDCOMPARE                              #
// #                                                                        #
// #  This program is free software; you can redistribute it and/or modify  #
// #  it under the terms of the GNU General Public License as published by  #
// #  the Free Software Foundation; version 2 or later of the License.      #
// #                                                                        #
// #  This program is distributed in the hope that it will be useful,       #
// #  but WITHOUT ANY WARRANTY; without even the implied warranty of        #
// #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the          #
// #  GNU General Public License for more details.                          #
// #                                                                        #
// #          COPYRIGHT: EDF R&D / TELECOM ParisTech (ENST-TSI)             #
// #                                                                        #
// ##########################################################################

#ifndef CC_DRAWABLE_OBJECT_HEADER
#define CC_DRAWABLE_OBJECT_HEADER

// Local
#include "ccGLDrawContext.h"

// CCCoreLib
#include <CCGeom.h>

class ccGenericGLDisplay;

//! Simple (clipping) plane equation
struct ccClipPlane
{
	Tuple4Tpl<double> equation;
};
using ccClipPlaneSet = std::vector<ccClipPlane>;

//! Generic interface for (3D) drawable entities
class QCC_DB_LIB_API ccDrawableObject
{
  public:
	//! Default constructor
	ccDrawableObject();
	//! Copy constructor
	ccDrawableObject(const ccDrawableObject& object);

	virtual ~ccDrawableObject() = default;

  public: // drawing and drawing options
	//! Draws entity and its children
	virtual void draw(CC_DRAW_CONTEXT& context) = 0;

	//! Returns whether entity is visible or not
	inline virtual bool isVisible() const
	{
		return m_visible;
	}
	//! Sets entity visibility
	inline virtual void setVisible(bool state)
	{
		m_visible = state;
	}
	//! Toggles visibility
	inline virtual void toggleVisibility()
	{
		setVisible(!isVisible());
	}

	//! Returns whether visibility is locked or not
	inline virtual bool isVisibilityLocked() const
	{
		return m_lockedVisibility;
	}
	//! Locks/unlocks visibility
	/** If visibility is locked, the user won't be able to modify it
	    (via the properties tree for instance).
	**/
	inline virtual void lockVisibility(bool state)
	{
		m_lockedVisibility = state;
	}

	//! Returns whether entity is selected or not
	inline virtual bool isSelected() const
	{
		return m_selected;
	}
	//! Selects/unselects entity
	inline virtual void setSelected(bool state)
	{
		m_selected = state;
	}

	//! Returns main OpenGL parameters for this entity
	/** These parameters are deduced from the visibility states
	    of its different features (points, normals, etc.).
	    \param params a glDrawParams structure
	**/
	virtual void getDrawingParameters(glDrawParams& params) const;

	//! Returns whether colors are enabled or not
	inline virtual bool hasColors() const
	{
		return false;
	}
	//! Returns whether colors are shown or not
	inline virtual bool colorsShown() const
	{
		return m_colorsDisplayed;
	}
	//! Sets colors visibility
	inline virtual void showColors(bool state)
	{
		m_colorsDisplayed = state;
	}
	//! Toggles colors display state
	inline virtual void toggleColors()
	{
		showColors(!colorsShown());
	}

	//! Returns whether normals are enabled or not
	inline virtual bool hasNormals() const
	{
		return false;
	}
	//! Returns whether normals are shown or not
	inline virtual bool normalsShown() const
	{
		return m_normalsDisplayed;
	}
	//! Sets normals visibility
	inline virtual void showNormals(bool state)
	{
		m_normalsDisplayed = state;
	}
	//! Toggles normals display state
	inline virtual void toggleNormals()
	{
		showNormals(!normalsShown());
	}

  public: // scalar fields
	//! Returns whether an active scalar field is available or not
	inline virtual bool hasDisplayedScalarField() const
	{
		return false;
	}

	//! Returns whether one or more scalar fields are instantiated
	/** \warning doesn't mean a scalar field is currently displayed
	    (see ccDrawableObject::hasDisplayedScalarField).
	**/
	inline virtual bool hasScalarFields() const
	{
		return false;
	}

	//! Sets active scalarfield visibility
	inline virtual void showSF(bool state)
	{
		m_sfDisplayed = state;
	}

	//! Toggles SF display state
	inline virtual void toggleSF()
	{
		showSF(!sfShown());
	}

	//! Returns whether active scalar field is visible
	inline virtual bool sfShown() const
	{
		return m_sfDisplayed;
	}

  public: //(Mesh) materials
	//! Toggles material display state
	virtual void toggleMaterials()
	{
	} // does nothing by default!

  public: // Name display in 3D
	//! Sets whether name should be displayed in 3D or not
	inline virtual void showNameIn3D(bool state)
	{
		m_showNameIn3D = state;
		if (!state)
			m_nameIn3DPosIsValid = false;
	}

	//! Returns whether name is displayed in 3D or not
	inline virtual bool nameShownIn3D() const
	{
		return m_showNameIn3D;
	}

	//! Toggles name in 3D display state
	inline virtual void toggleShowName()
	{
		showNameIn3D(!nameShownIn3D());
	}

  public: // Temporary color
	//! Returns whether colors are currently overridden by a temporary (unique) color
	/** See ccDrawableObject::setTempColor.
	 **/
	inline virtual bool isColorOverridden() const
	{
		return m_colorIsOverridden;
	}

	//! Returns current temporary (unique) color
	inline virtual const ccColor::Rgba& getTempColor() const
	{
		return m_tempColor;
	}

	//! Sets current temporary (unique)
	/** \param col rgba color
	    \param autoActivate auto activates temporary color
	**/
	virtual void setTempColor(const ccColor::Rgba& col, bool autoActivate = true);

	//! Sets current temporary (unique)
	/** \param col rgb color
	    \param autoActivate auto activates temporary color
	**/
	virtual void setTempColor(const ccColor::Rgb& col, bool autoActivate = true);

	//! Set temporary color activation state
	inline virtual void enableTempColor(bool state)
	{
		m_colorIsOverridden = state;
	}

  public: // associated display management
	//! Unlinks entity from a GL display (only if it belongs to it of course)
	virtual void removeFromDisplay(const ccGenericGLDisplay* win);

	//! Sets associated GL display
	virtual void setDisplay(ccGenericGLDisplay* win);

	//! Returns associated GL display
	inline virtual ccGenericGLDisplay* getDisplay() const
	{
		return m_currentDisplay;
	}

	//! Redraws associated GL display
	virtual void redrawDisplay();

	//! Sets associated GL display 'refreshable' before global refresh
	/** Only tagged displays will be refreshed when ccGenericGLDisplay::refresh
	    is called (see also MainWindow::RefreshAllGLWindow,
	    MainWindow::refreshAll and ccDrawableObject::refreshDisplay).
	**/
	virtual void prepareDisplayForRefresh();

	//! Refreshes associated GL display
	/** See ccGenericGLDisplay::refresh. The display will only be updated
	    if it has been 'prepared for refresh' (see prepareDisplayForRefresh).
	**/
	virtual void refreshDisplay(bool only2D = false);

  public: // Transformation matrix management (for display only)
	//! Associates entity with a GL transformation (rotation + translation)
	/** \warning FOR DISPLAY PURPOSE ONLY (i.e. should only be temporary)
	    If the associated GL transformation is enabled (see
	    ccDrawableObject::enableGLTransformation), it will
	    be applied before displaying this entity.
	    However it will not be taken into account by any CCCoreLib algorithm
	    (distance computation, etc.) for instance.
	    Note: GL transformation is automatically enabled.
	**/
	virtual void setGLTransformation(const ccGLMatrix& trans);

	//! Enables/disables associated GL transformation
	/** See ccDrawableObject::setGLTransformation.
	 **/
	virtual void enableGLTransformation(bool state);

	//! Returns whether a GL transformation is enabled or not
	inline virtual bool isGLTransEnabled() const
	{
		return m_glTransEnabled;
	}

	//! Returns associated GL transformation
	/** See ccDrawableObject::setGLTransformation.
	 **/
	inline virtual const ccGLMatrix& getGLTransformation() const
	{
		return m_glTrans;
	}

	//! Resets associated GL transformation
	/** GL transformation is reset to identity.
	    Note: GL transformation is automatically disabled.
	    See ccDrawableObject::setGLTransformation.
	**/
	virtual void resetGLTransformation();

	//! Multiplies (left) current GL transformation by a rotation matrix
	/** 'GLtrans = M * GLtrans'
	    Note: GL transformation is automatically enabled.
	    See ccDrawableObject::setGLTransformation.
	**/
	virtual void rotateGL(const ccGLMatrix& rotMat);

	//! Translates current GL transformation by a rotation matrix
	/** 'GLtrans = GLtrans + T'
	    Note: GL transformation is automatically enabled.
	    See ccDrawableObject::setGLTransformation.
	**/
	virtual void translateGL(const CCVector3& trans);

  public: // clipping planes
	//! Removes all clipping planes (if any)
	virtual void removeAllClipPlanes()
	{
		m_clipPlanes.resize(0);
	}

	//! Registers a new clipping plane
	/** \return false if the planes couldn't be added (not enough memory)
	 **/
	virtual bool addClipPlanes(const ccClipPlane& plane);

	//! Enables or disables clipping planes (OpenGL)
	/** \warning If enabling the clipping planes, be sure to call this method AFTER the modelview matrix has been set.
	 **/
	virtual void toggleClipPlanes(CC_DRAW_CONTEXT& context, bool enable);

  public: // push and pop display state
	//! Display state
	struct DisplayState
	{
		DisplayState()
		{
		}
		DisplayState(const ccDrawableObject& dobj);

		using Shared = QSharedPointer<DisplayState>;

		bool                visible           = false;
		bool                colorsDisplayed   = false;
		bool                normalsDisplayed  = false;
		bool                sfDisplayed       = false;
		bool                colorIsOverridden = false;
		bool                showNameIn3D      = false;
		ccGenericGLDisplay* display           = nullptr;
	};

	//! Pushes the current display state
	virtual bool pushDisplayState();

	//! Pops the last pushed display state
	virtual void popDisplayState(bool apply = true);

	//! Applies a display state
	virtual void applyDisplayState(const DisplayState& state);

  protected: // members
	//! Specifies whether the object is visible or not
	/** Note: this does not influence the children visibility
	 **/
	bool m_visible;

	//! Specifies whether the object is selected or not
	bool m_selected;

	//! Specifies whether the visibility can be changed by user or not
	bool m_lockedVisibility;

	//! Specifies whether colors should be displayed
	bool m_colorsDisplayed;
	//! Specifies whether normals should be displayed
	bool m_normalsDisplayed;
	//! Specifies whether scalar field should be displayed
	bool m_sfDisplayed;

	//! Temporary (unique) color
	ccColor::Rgba m_tempColor;
	//! Temporary (unique) color activation state
	bool m_colorIsOverridden;

	//! Current GL transformation
	/** See ccDrawableObject::setGLTransformation.
	 **/
	ccGLMatrix m_glTrans;
	//! Current GL transformation activation state
	/** See ccDrawableObject::setGLTransformation.
	 **/
	bool m_glTransEnabled;

	//! Whether name is displayed in 3D or not
	bool m_showNameIn3D;
	//! Last 2D position of the '3D' name
	CCVector3d m_nameIn3DPos;
	//! Whether the last 2D position of the '3D' name is valid or not
	bool m_nameIn3DPosIsValid;

	//! Currently associated GL display
	ccGenericGLDisplay* m_currentDisplay;

	//! Active clipping planes (used for display only)
	ccClipPlaneSet m_clipPlanes;

	//! The stack of pushed display states
	std::vector<DisplayState::Shared> m_displayStateStack;
};

#endif // CC_DRAWABLE_OBJECT_HEADER
